package com.managertrade.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.managertrade.config.MultiPeriodCache;
import com.managertrade.dto.vo.KlineWithIndicators;
import com.managertrade.util.IndicatorUtil;
import com.managertrade.util.OkxProxyHttpClient;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.net.ssl.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class KlineService {
    @Autowired
    private MultiPeriodCache multiPeriodCache;

    public List<KlineWithIndicators> getKlineWithIndicators(String symbol, String interval, Integer limit,String source,Long start, Long end) throws Exception {
        List<KlineWithIndicators> rawKlines =  new ArrayList<>();
        if("binance".equals(source)){
            rawKlines = fetchKlineFromBinance(symbol, interval, limit,start,end);
        }else{
            rawKlines = fetchKlineFromOkx(symbol, interval, limit);
        }
        List<Double> closePrices = rawKlines.stream().map(KlineWithIndicators::getClose).collect(Collectors.toList());
        // 指标计算
        List<Double> ema7 = IndicatorUtil.ema(closePrices, 7);
        List<Double> ema21 = IndicatorUtil.ema(closePrices, 21);
        IndicatorUtil.MacdResult macdResult = IndicatorUtil.macd(closePrices, 12, 26, 9);
        IndicatorUtil.BollResult bollResult = IndicatorUtil.boll(closePrices, 20);

        for (int i = 0; i < rawKlines.size(); i++) {
            KlineWithIndicators k = rawKlines.get(i);
            if (i < ema7.size()) k.setEma7(ema7.get(i));
            if (i < ema21.size()) k.setEma21(ema21.get(i));
            if (i < macdResult.macd.size()) {
                k.setMacd(macdResult.macd.get(i));
                k.setSignal(macdResult.signal.get(i));
                k.setHistogram(macdResult.histogram.get(i));
            }
            if (i < bollResult.upper.size()) {
                k.setBollUpper(bollResult.upper.get(i));
                k.setBollMiddle(bollResult.middle.get(i));
                k.setBollLower(bollResult.lower.get(i));
            }
        }
        KlineWithIndicators.calculateIndicators(rawKlines);
        return rawKlines;
    }

    private List<KlineWithIndicators> fetchKlineFromOkx(String symbol, String interval, int limit) throws Exception {
        // OKX API REST 示例：https://www.okx.com/api/v5/market/candles?instId=BTC-USDT&bar=1H&limit=100
        Map<String,String> map = new HashMap<>();
        if("1h,2h,4h,6h,12h,1d,1w".contains(interval)){
            interval = interval.toUpperCase();
        }
        map.put("instId",symbol);
        map.put("bar",interval);
        //limit 最大300条
        map.put("limit",String.valueOf(limit));
        String result = OkxProxyHttpClient.get("https://www.okx.com/api/v5/market/candles", map);

        List<KlineWithIndicators> klineWithIndicators = parseOkxData(symbol,interval,result);
        return klineWithIndicators;
    }

    private List<KlineWithIndicators> fetchKlineFromBinance(String symbol, String interval, Integer limit,Long start, Long end) throws Exception {
        // binance 示例：https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1h&limit=100
        Map<String,String> map = new HashMap<>();
        map.put("symbol",symbol.replace("-",""));
        map.put("interval",interval);
        if(limit != null){
            map.put("limit",String.valueOf(limit));
        }

        if(end !=null || start!=null ){
            map.put("endTime", String.valueOf(end));
            map.put("startTime", String.valueOf(start));
        }
        this.disableSslVerification();
        String result = OkxProxyHttpClient.get("https://api.binance.com/api/v3/klines", map);

        List<KlineWithIndicators> klineWithIndicators = parseBinanceKlines(symbol,interval,result);
        return klineWithIndicators;
    }
    public static void disableSslVerification() {
        try {
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
                        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
                        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
                    }
            };

            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HostnameVerifier allHostsValid = (hostname, session) -> true;
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static List<KlineWithIndicators> parseOkxData(String symbol, String interval,String jsonResponse) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readTree(jsonResponse);
        JsonNode dataNode = rootNode.get("data");
        if (dataNode == null || !dataNode.isArray()) {
            throw new RuntimeException("Invalid OKX data format");
        }
        List<KlineWithIndicators> klines = new ArrayList<>();
        // OKX 返回的是时间倒序，业务通常用时间正序，所以这里 add(0, kline) 逆转顺序
        for (JsonNode item : dataNode) {
            KlineWithIndicators k = new KlineWithIndicators();
            k.setPeriod(interval);
            k.setSymbol(symbol);
            // item 是数组: [timestamp, open, high, low, close, volume, ...]
            k.setTimestamp(item.get(0).asLong());
            k.setOpen(Double.parseDouble(item.get(1).asText()));
            k.setHigh(Double.parseDouble(item.get(2).asText()));
            k.setLow(Double.parseDouble(item.get(3).asText()));
            k.setClose(Double.parseDouble(item.get(4).asText()));
            k.setVolume(Double.parseDouble(item.get(5).asText()));
            klines.add(0, k); // 逆序插入
        }
        return klines;
    }

    public static List<KlineWithIndicators> parseBinanceKlines(String symbol, String interval,String json) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode arrayNode = mapper.readTree(json);

        List<KlineWithIndicators> result = new ArrayList<>();

        for (JsonNode item : arrayNode) {
            KlineWithIndicators kline = new KlineWithIndicators();
            kline.setSymbol(symbol);
            kline.setPeriod(interval);
            kline.setTimestamp(item.get(0).asLong());
            kline.setOpen(Double.parseDouble(item.get(1).asText()));
            kline.setHigh(Double.parseDouble(item.get(2).asText()));
            kline.setLow(Double.parseDouble(item.get(3).asText()));
            kline.setClose(Double.parseDouble(item.get(4).asText()));
            kline.setVolume(Double.parseDouble(item.get(5).asText()));

            // 其余指标为 null，稍后计算后填充
            kline.setEma7(null);
            kline.setEma21(null);
            kline.setMacd(null);
            kline.setSignal(null);
            kline.setHistogram(null);
            kline.setBollUpper(null);
            kline.setBollMiddle(null);
            kline.setBollLower(null);

            result.add(kline);
        }

        return result;
    }


    public void saveOrUpdate(String symbol, String period, List<KlineWithIndicators> klines) throws IOException {
        if (klines == null || klines.isEmpty()) return;

        String projectDir = System.getProperty("user.dir");
        File projectFile = new File(projectDir);
        File parentDir = projectFile.getParentFile();
        String dataDirPath = new File(parentDir, "data").getAbsolutePath();

        File dataDir = new File(dataDirPath);
        if (!dataDir.exists()) {
            dataDir.mkdirs();
        }
        String fileName = dataDirPath + "/" + symbol + "_kline_" + period + ".csv";
        File file = new File(fileName);

        // === 读取旧数据 ===
        List<KlineWithIndicators> allKlines = new ArrayList<>();
        if (file.exists()) {
            try (BufferedReader br = new BufferedReader(new FileReader(file))) {
                String line;
                br.readLine(); // 跳过表头
                while ((line = br.readLine()) != null) {
                    String[] parts = line.split(",");
                    if (parts.length < 18) continue;
                    KlineWithIndicators kline = new KlineWithIndicators();
                    kline.setSymbol(parts[0]);
                    kline.setPeriod(parts[1]);
                    kline.setUniqueId(parts[2]);
                    kline.setDatetime(parts[3]);
                    kline.setTimestamp(Long.parseLong(parts[4]));
                    kline.setOpen(Double.parseDouble(parts[5]));
                    kline.setHigh(Double.parseDouble(parts[6]));
                    kline.setLow(Double.parseDouble(parts[7]));
                    kline.setClose(Double.parseDouble(parts[8]));
                    kline.setVolume(Double.parseDouble(parts[9]));
                    kline.setEma7(parseNullable(parts[10]));
                    kline.setEma21(parseNullable(parts[11]));
                    kline.setMacd(parseNullable(parts[12]));
                    kline.setSignal(parseNullable(parts[13]));
                    kline.setHistogram(parseNullable(parts[14]));
                    kline.setBollUpper(parseNullable(parts[15]));
                    kline.setBollMiddle(parseNullable(parts[16]));
                    kline.setBollLower(parseNullable(parts[17]));
                    allKlines.add(kline);
                }
            }
        }

        // === 合并新旧数据 ===
        allKlines.addAll(klines);

        // === 去重（根据 uniqueId） ===
        allKlines = new ArrayList<>(
                allKlines.stream()
                        .collect(Collectors.toMap(
                                KlineWithIndicators::getUniqueId,
                                Function.identity(),
                                (existing, replacement) -> replacement
                        ))
                        .values()
        );

        // === 按时间升序排序 ===
        allKlines.sort(Comparator.comparingLong(KlineWithIndicators::getTimestamp));

        // === 计算指标 ===
        List<Double> closePrices = allKlines.stream().map(KlineWithIndicators::getClose).collect(Collectors.toList());
        List<Double> ema7 = IndicatorUtil.ema(closePrices, 7);
        List<Double> ema21 = IndicatorUtil.ema(closePrices, 21);
        IndicatorUtil.MacdResult macdResult = IndicatorUtil.macd(closePrices, 12, 26, 9);
        IndicatorUtil.BollResult bollResult = IndicatorUtil.boll(closePrices, 20);

        for (int i = 0; i < allKlines.size(); i++) {
            KlineWithIndicators k = allKlines.get(i);
            if (i < ema7.size()) k.setEma7(ema7.get(i));
            if (i < ema21.size()) k.setEma21(ema21.get(i));
            if (i < macdResult.macd.size()) {
                k.setMacd(macdResult.macd.get(i));
                k.setSignal(macdResult.signal.get(i));
                k.setHistogram(macdResult.histogram.get(i));
            }
            if (i < bollResult.upper.size()) {
                k.setBollUpper(bollResult.upper.get(i));
                k.setBollMiddle(bollResult.middle.get(i));
                k.setBollLower(bollResult.lower.get(i));
            }
        }

        // === 覆盖写入 CSV ===
        try (PrintWriter pw = new PrintWriter(new FileWriter(file))) {
            // 表头
            pw.println("symbol,period,uniqueId,dateTime,timestamp,open,high,low,close,volume," +
                    "ema7,ema21,macd,signal,histogram,bollUpper,bollMiddle,bollLower");

            for (KlineWithIndicators k : allKlines) {
                pw.printf("%s,%s,%s,%s,%d,%.8f,%.8f,%.8f,%.8f,%.8f,%s,%s,%s,%s,%s,%s,%s,%s%n",
                        k.getSymbol(), k.getPeriod(), k.getUniqueId(), k.getDatetime(), k.getTimestamp(),
                        k.getOpen(), k.getHigh(), k.getLow(), k.getClose(), k.getVolume(),
                        formatNullable(k.getEma7()), formatNullable(k.getEma21()),
                        formatNullable(k.getMacd()), formatNullable(k.getSignal()), formatNullable(k.getHistogram()),
                        formatNullable(k.getBollUpper()), formatNullable(k.getBollMiddle()), formatNullable(k.getBollLower())
                );
            }
        }

        KlineWithIndicators latest = allKlines.stream()
                .max(Comparator.comparingLong(KlineWithIndicators::getTimestamp))
                .orElse(null);

        // === 同步缓存 ===
        if (latest != null) {
            multiPeriodCache.put(symbol + "_" + period, latest.getTimestamp());
        }
    }


    private static String formatNullable(Double val) {
        return (val == null) ? "" : String.valueOf(val);
    }

    private static Double parseNullable(String s) {
        return (s == null || s.isEmpty()) ? null : Double.parseDouble(s);
    }

    // 填充行数据
    private void fillRow(Row row, KlineWithIndicators k) {
        int col = 0;
        row.createCell(col++).setCellValue(k.getUniqueId());
        row.createCell(col++).setCellValue(k.getSymbol());
        row.createCell(col++).setCellValue(k.getPeriod());
        row.createCell(col++).setCellValue(k.getTimestamp());
        row.createCell(col++).setCellValue(k.getOpen());
        row.createCell(col++).setCellValue(k.getHigh());
        row.createCell(col++).setCellValue(k.getLow());
        row.createCell(col++).setCellValue(k.getClose());
        row.createCell(col++).setCellValue(k.getVolume());
        row.createCell(col++).setCellValue(k.getEma7() != null ? k.getEma7() : 0);
        row.createCell(col++).setCellValue(k.getEma21() != null ? k.getEma21() : 0);
        row.createCell(col++).setCellValue(k.getMacd() != null ? k.getMacd() : 0);
        row.createCell(col++).setCellValue(k.getSignal() != null ? k.getSignal() : 0);
        row.createCell(col++).setCellValue(k.getHistogram() != null ? k.getHistogram() : 0);
        row.createCell(col++).setCellValue(k.getBollUpper() != null ? k.getBollUpper() : 0);
        row.createCell(col++).setCellValue(k.getBollMiddle() != null ? k.getBollMiddle() : 0);
        row.createCell(col++).setCellValue(k.getBollLower() != null ? k.getBollLower() : 0);
    }
}
